package fge;

import com.mxgraph.model.mxCell;
import com.mxgraph.model.mxGeometry;
import com.mxgraph.model.mxGraphModel;
import com.mxgraph.model.mxICell;
import com.mxgraph.swing.handler.mxKeyboardHandler;
import com.mxgraph.swing.handler.mxRubberband;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.swing.mxGraphOutline;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxEvent;
import com.mxgraph.util.mxEventObject;
import com.mxgraph.util.mxEventSource.mxIEventListener;
import com.mxgraph.util.mxPoint;
import com.mxgraph.util.mxRectangle;
import com.mxgraph.util.mxResources;
import com.mxgraph.util.mxUndoManager;
import com.mxgraph.util.mxUndoableEdit;
import com.mxgraph.util.mxUndoableEdit.mxUndoableChange;
import com.mxgraph.view.mxGraph;
import com.mxgraph.view.mxGraphSelectionModel;
import com.mxgraph.view.mxStylesheet;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JFileChooser;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.KeyStroke;
import fge.Main.UserSuppliedNoFile;

public class Main extends JFrame {

	private static final long serialVersionUID = -2707712944901661772L;
	protected mxGraph graph;
	protected mxGraphComponent graphComponent;
	private mxKeyboardHandler keyboardHandler;
	protected Action fooAction;
	protected JTabbedPane libraryPane;
	protected String fileName;
	protected mxGraphOutline graphOutline;
	protected TreeView treeview;
	protected mxRubberband rubberband;
	protected LibraryPalette customPalette;
	private final propPanel sheet;
	protected mxUndoManager undoManager;
	protected mxIEventListener undoHandler = new mxIEventListener() {

		public void invoke(Object source, mxEventObject evt) {
			undoManager.undoableEditHappened((mxUndoableEdit) evt.getProperty("edit"));
		}
	};

	public class CustomAddAction extends AbstractAction {

		private Main mm;

		public CustomAddAction(String text, Main m) {
			super(text);
			this.mm = m;
		}

		public void actionPerformed(ActionEvent e) {
			if (mm.graph.getSelectionCells().length != 1) {
				JOptionPane.showMessageDialog(null, "Can only add a single component to the library at a time");
				return;
			}
			
			Object obj = mm.graph.getSelectionCell();	// Only 1 (heirachical) cell
			if (obj == null) {
				return;
			}

			// We store a recursive deep copy of the selected cell in the Custom library
			Object[] deep_copy_cells = mm.graph.cloneCells(new Object[]{obj}, false);
			if (deep_copy_cells.length != 1) { // Only a single (heirachical) cell should be added at a time to the Custom library
				return;
			}
			Object deep_copy_cell = deep_copy_cells[0];
			if (!(deep_copy_cell instanceof mxCell)) {
				return;
			}
			mxCell deep_copy = (mxCell) deep_copy_cell;

			mm.customPalette.addTemplate(
					deep_copy_cell.toString(),
					"/fge/images/rounded_custom.png",
					deep_copy);
		}
	}

	public class RotateAction extends AbstractAction {

		private Main mm;
		private int increment;

		public RotateAction(String text, Main m, int increment) {
			super(text);
			this.mm = m;
			this.increment = increment;
		}

		public void actionPerformed(ActionEvent e) {
			Object objs[] = mm.graph.getSelectionCells();

			for (Object obj : objs) {
				if (!(obj instanceof Fcomponent)) {
					continue;
				}
				Fcomponent cell = (Fcomponent) obj;
				cell.setRotation(cell.getRotation() + increment);
			}

			mm.graph.repaint();
			mm.graph.refresh();
			mm.graphComponent.refresh();
			mm.graphComponent.repaint();
		}
	}

	public class DeleteAction extends AbstractAction {

		private Main mm;

		public DeleteAction(String text, Main m) {
			super(text);
			this.mm = m;
		}

		public void actionPerformed(ActionEvent e) {
			mm.graph.removeCells();
			System.out.println("Deleted selection");
		}
	}

	public class HaskellExportAction extends AbstractAction {

		private Main mm;

		public HaskellExportAction(String text, Main m) {
			super(text);
			this.mm = m;
		}

		public void actionPerformed(ActionEvent e) {
			try {
				mm.HaskellSave();
			} catch (RuntimeException ex) {
				JOptionPane.showMessageDialog(null, ex.getMessage(), "Export error", JOptionPane.ERROR_MESSAGE);
			}
		}
	}

	/**
	 * Gets the haskell code for fv and its children.
	 * This visits in post-order to make sure children have been defined before use in their parent
	 * @param fv Root component 
	 */
	private String haskell_recur(Fcomponent fv) {
		String ret = "";

		List<Fcomponent> children = fv.getChildComponents();
		for (Fcomponent v : children) {
			ret += haskell_recur(v);
		}

		ret += fv.getHaskellCode();
		ret += System.getProperty("line.separator");

		return ret;
	}

	private String HaskellExport() {
		String ret = "";
		mxCell root = (mxCell) this.graph.getDefaultParent();

		for (int i = 0; i < root.getChildCount(); i++) {
			mxICell c = root.getChildAt(i);
			if (!(c instanceof Fcomponent)) {
				continue;
			}

			Fcomponent fc = (Fcomponent) c;
			ret += haskell_recur(fc);
		}

		System.out.println("Haskell export:");
		System.out.println(ret);

		return ret;
	}

	private void HaskellSave() {
		String hfile = null;

		if (hfile == null) {
			try {
				hfile = this.SaveDialog();
			} catch (UserSuppliedNoFile ex) {
				return;	// The user didn't supply any file name, so simply don't save anything
			}
		}

		String HaskellCode = this.HaskellExport();

		try {
			BufferedWriter out = new BufferedWriter(new FileWriter(hfile));
			out.write(HaskellCode);
			out.close();
		} catch (IOException e) {
			JOptionPane.showMessageDialog(null,
					"Could not export to file '" + hfile + "'.",
					"File I/O error",
					JOptionPane.ERROR_MESSAGE);
		}
	}

	public class SaveAction extends AbstractAction {

		private Main mm;
		private boolean save_as;

		public SaveAction(String text, Main m, boolean save_as) {
			super(text);
			this.mm = m;
			this.save_as = save_as;
		}

		public void actionPerformed(ActionEvent e) {
			mm.save(this.save_as);
		}
	}

	public class OpenAction extends AbstractAction {

		private Main mm;

		public OpenAction(String text, Main m) {
			super(text);
			this.mm = m;
		}

		public void actionPerformed(ActionEvent e) {
			mm.open();
		}
	}

	public class UserSuppliedNoFile extends Exception {

		public UserSuppliedNoFile() {
			super();
		}
	}

	public String SaveDialog() throws UserSuppliedNoFile {
		JFileChooser fc = new JFileChooser();
		int rc = fc.showDialog(null, "Save");
		if (rc != JFileChooser.APPROVE_OPTION) {
			throw new UserSuppliedNoFile();
		}

		String file = fc.getSelectedFile().getAbsolutePath();

		if (new File(file).exists()) {
			int ret = JOptionPane.showConfirmDialog(null, "Overwrite existing file?");
			if (ret != JOptionPane.YES_OPTION) {
				throw new UserSuppliedNoFile();
			}
		}
		return file;
	}

	public String OpenDialog() throws UserSuppliedNoFile {
		JFileChooser fc = new JFileChooser();
		
		try {
			fc.setCurrentDirectory(new File(new File(".").getCanonicalPath()));
		} catch (IOException ex) {
			Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
		}	
		
		int rc = fc.showDialog(null, "Open");
		if (rc != JFileChooser.APPROVE_OPTION) {
			throw new UserSuppliedNoFile();
		}

		String file = fc.getSelectedFile().getAbsolutePath();

		if (!(new File(file).exists())) {
			JOptionPane.showMessageDialog(null,
					"Could not open file '" + file + "'.",
					"File I/O error",
					JOptionPane.ERROR_MESSAGE);
			throw new UserSuppliedNoFile();
		}

		return file;
	}

	private void save(boolean always_show) {
		String file = this.fileName;

		if (file == null || always_show) {
			try {
				file = this.SaveDialog();
			} catch (UserSuppliedNoFile ex) {
				return;	// The user didn't supply any file name, so simply don't save anything
			}
		}

		Object children[] = graph.getChildCells(graph.getDefaultParent());
		XStream xstream = new XStream(new DomDriver());
		xstream.setMode(XStream.ID_REFERENCES);	// relative or absolute references can grow quite deep (=> large xml); prefer flat ids instead

		try {
			BufferedWriter out = new BufferedWriter(new FileWriter(file));
			xstream.toXML(children, out);
			out.close();
			this.fileName = file;	// File written succesfully, update the current file
		} catch (IOException e) {
			JOptionPane.showMessageDialog(null,
					"Could not save to file '" + file + "'.",
					"File I/O error",
					JOptionPane.ERROR_MESSAGE);
		}
	}

	private void open() {
		String file;

		try {
			file = this.OpenDialog();
		} catch (UserSuppliedNoFile ex) {
			return;	// The user didn't supply any file name, so simply don't open anything
		}

		graph.removeCells(graph.getChildVertices(graph.getDefaultParent()));	// Clear canvas

		XStream xstream = new XStream(new DomDriver());
		xstream.setMode(XStream.ID_REFERENCES);	// relative or absolute references can grow quite deep (=> large xml); prefer flat ids instead
		Object children[] = (Object[]) xstream.fromXML(new File(file));
		for (Object c : children) {
			graph.addCell(c);
		}

		this.fileName = file;	// Remember the file we've opened
	}

	public class unredoAction extends AbstractAction {

		private Main mm;
		private boolean undo;

		/**
		 * @param text	Label to display in menu
		 * @param m		Editor to perform the redo/undo action in
		 * @param undo	True if Undo, false if redo
		 */
		public unredoAction(String text, Main m, boolean undo) {
			super(text);
			this.mm = m;
			this.undo = undo;
		}

		public void actionPerformed(ActionEvent e) {
			if (undo) {
				mm.undoManager.undo();
			} else {
				mm.undoManager.redo();
			}
		}
	}

	protected void mouseWheelZoom(MouseWheelEvent e) {
		if (e.getWheelRotation() < 0) {
			graphComponent.zoomIn();
		} else {
			graphComponent.zoomOut();
		}

		System.out.println(mxResources.get("scale") + ": "
				+ (int) (100 * graphComponent.getGraph().getView().getScale())
				+ "%");
	}

	public final LibraryPalette insertPalette(String title, boolean editable) {
		if (this.libraryPane == null) {
			this.libraryPane = new JTabbedPane();
		}
		
		if (this.libraryPane.indexOfTab(title) != -1) {
			JOptionPane.showMessageDialog(null, "Can't add library with existing name!");
			return null;
		}
		
		final LibraryPalette palette = new LibraryPalette(this, editable, title);
		final JScrollPane scrollPane = new JScrollPane(palette);
		scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
		scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
		this.libraryPane.add(title, scrollPane);

		// Updates the widths of the palettes if the container size changes
		this.libraryPane.addComponentListener(new ComponentAdapter() {

			@Override
			public void componentResized(ComponentEvent e) {
				int w = scrollPane.getWidth() - scrollPane.getVerticalScrollBar().getWidth();
				palette.setPreferredWidth(w);
			}
		});

		return palette;
	}

	private void applyEdgeDefaults() {
		// Settings for edges
		Map<String, Object> edge = new HashMap<String, Object>();
		edge.put(mxConstants.STYLE_ROUNDED, true);
		edge.put(mxConstants.STYLE_ORTHOGONAL, false);
//		edge.put(mxConstants.STYLE_EDGE, "elbowEdgeStyle");
//		edge.put(mxConstants.STYLE_EDGE, "entityRelationEdgeStyle");
		edge.put(mxConstants.STYLE_SHAPE, mxConstants.SHAPE_CONNECTOR);
		edge.put(mxConstants.STYLE_ENDARROW, mxConstants.ARROW_CLASSIC);
		edge.put(mxConstants.STYLE_VERTICAL_ALIGN, mxConstants.ALIGN_MIDDLE);
		edge.put(mxConstants.STYLE_ALIGN, mxConstants.ALIGN_CENTER);
		edge.put(mxConstants.STYLE_STROKECOLOR, "#000000"); // default is #6482B9
		edge.put(mxConstants.STYLE_FONTCOLOR, "#446299");

		mxStylesheet edgeStyle = new mxStylesheet();
		edgeStyle.setDefaultEdgeStyle(edge);
		graph.setStylesheet(edgeStyle);
	}

	private void createMenu() {
		JMenuBar bar = new JMenuBar();

		{
			JMenu menu = new JMenu("File");
			menu.setMnemonic(KeyEvent.VK_F);	// Alt+F is accelerator

			menu.add(new JMenuItem("New", 'N')).setAccelerator(KeyStroke.getKeyStroke('N', java.awt.Event.CTRL_MASK));
			menu.add(new JMenuItem(new OpenAction("Open", this))).setAccelerator(KeyStroke.getKeyStroke('O', java.awt.Event.CTRL_MASK));
			menu.add(new JMenuItem(new SaveAction("Save", this, false))).setAccelerator(KeyStroke.getKeyStroke('S', java.awt.Event.CTRL_MASK));
			menu.add(new JMenuItem(new SaveAction("Save As", this, true))).setAccelerator(KeyStroke.getKeyStroke('S', java.awt.Event.CTRL_MASK + java.awt.Event.SHIFT_MASK));
			menu.add(new JMenuItem(new HaskellExportAction("Export to Haskell", this))); // .setAccelerator(KeyStroke.getKeyStroke('S', java.awt.Event.CTRL_MASK + java.awt.Event.SHIFT_MASK));

			bar.add(menu);
		}

		{
			JMenu menu = new JMenu("Edit");
			menu.setMnemonic(KeyEvent.VK_E);	// Alt+E is accelerator

			menu.add(new JMenuItem(new RotateAction("Rotate selection", this, 1))).setAccelerator(KeyStroke.getKeyStroke('R', java.awt.Event.CTRL_MASK));
			menu.add(new JMenuItem(new RotateAction("Rotate selection", this, -1))).setAccelerator(KeyStroke.getKeyStroke('R', java.awt.Event.CTRL_MASK + java.awt.Event.SHIFT_MASK));
			menu.add(new JMenuItem(new CustomAddAction("Add selection to custom library", this)));// .setAccelerator(KeyStroke.getKeyStroke('G', java.awt.Event.CTRL_MASK));
			menu.add(new JMenuItem(new DeleteAction("Delete", this))).setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0));
			menu.add(new JMenuItem(new unredoAction("Undo", this, true))).setAccelerator(KeyStroke.getKeyStroke('Z', java.awt.Event.CTRL_MASK));
			menu.add(new JMenuItem(new unredoAction("Redo", this, false))).setAccelerator(KeyStroke.getKeyStroke('Y', java.awt.Event.CTRL_MASK));

			bar.add(menu);
		}

		this.setJMenuBar(bar);
	}

	private void installUndoRedo() {
		this.undoManager = new mxUndoManager();
		// Adds the command history to the model and view
		graph.getModel().addListener(mxEvent.UNDO, undoHandler);
		graph.getView().addListener(mxEvent.UNDO, undoHandler);

		// Keeps the selection in sync with the command history
		mxIEventListener uh = new mxIEventListener() {

			public void invoke(Object source, mxEventObject evt) {
				List<mxUndoableChange> changes = ((mxUndoableEdit) evt.getProperty("edit")).getChanges();
				graph.setSelectionCells(graph.getSelectionCellsForChanges(changes));
			}
		};

		undoManager.addListener(mxEvent.UNDO, uh);
		undoManager.addListener(mxEvent.REDO, uh);
	}
	
	/**
	 * This scrolls the viewpane such that the cell is visible, makes it selected
	 */
	void selectAndShow(mxCell c) {
		graphComponent.scrollCellToVisible(c, true);
		graph.setSelectionCell(c);
	}

	public Main() {
		super("ForSyDe graph editor");

		/**
		 * Okay, so in JGraphX version 1.5.1.10 a bug in mxGraphModel was introduced, which caused child edges to not being copied.
		 * The bug-report: http://forum.jgraph.com/questions/3521/15110-bug-report
		 * The currently used version 1.9.1.3 also has this bug, so I need to customize mxGraphModel to simply use the working version as found in 1.5.1.9
		 */
		mxGraphModel bugfix = new mxGraphModel() {

			// This is simply the 1.5.1.9 version of updateEdgeParent
			@Override
			public void updateEdgeParent(Object edge, Object root) {
				Object source = getTerminal(edge, true);
				Object target = getTerminal(edge, false);
				Object cell = null;

				if (isAncestor(root, source) && isAncestor(root, target)) {
					if (source == target) {
						cell = getParent(source);
					} else {
						cell = getNearestCommonAncestor(source, target);
					}

					// Keeps the edge in the same layer
					if (cell != null
							&& (getParent(cell) != root || isAncestor(cell, edge))
							&& getParent(edge) != cell) {
						mxGeometry geo = getGeometry(edge);

						if (geo != null) {
							mxPoint origin1 = getOrigin(getParent(edge));
							mxPoint origin2 = getOrigin(cell);

							double dx = origin2.getX() - origin1.getX();
							double dy = origin2.getY() - origin1.getY();

							geo = (mxGeometry) geo.clone();
							geo.translate(-dx, -dy);
							setGeometry(edge, geo);
						}

						add(cell, edge, getChildCount(cell));
					}
				}
			}
		};

		// mxGraph contains the mathematical graph, including properties about vertices and edges
		graph = new mxGraph(bugfix, null) {

			// Governs the folding and display of collapse-icon
			@Override
			public boolean isCellFoldable(Object cell, boolean collapse) {
				boolean ret = false;
				if (cell instanceof Fcomponent) {
					ret = ((Fcomponent) cell).isHierarchical();
				}
				return ret;
			}
		};

		graph.setMinimumGraphSize(new mxRectangle(0, 0, 800, 800));
		graph.setGridEnabled(true);
		graph.setGridSize(10);
		graph.setAllowDanglingEdges(false);		// Unconnected edges not allowed
		graph.setMultigraph(false);				// Only 1 edge from A to B is allowed
		graph.setDisconnectOnMove(false);
		graph.setSplitEnabled(false);
		this.applyEdgeDefaults();


		// This handles changes to the graph in the mathematical sense
		// The JTree is also updated on property changes
		graph.getModel().addListener(mxEvent.CHANGE, new mxIEventListener() {
			@Override
			public void invoke(Object sender, mxEventObject evt) {
				Main.this.treeview.update();
			}
		});


		// This handles selection events (by mouse selection or Ctrl+A)
		graph.getSelectionModel().addListener(mxEvent.CHANGE, new mxIEventListener() {

			@Override
			public void invoke(Object sender, mxEventObject evt) {
				//	System.out.println("Selection in graph component");
				if (sender instanceof mxGraphSelectionModel) {
					// Default to null as this will clear the property editor
					Object vf = null;
					Object[] selected = ((mxGraphSelectionModel) sender).getCells();

					// If multiple mxCells are selected, no property
					if (selected.length == 1) {
						Object cell = selected[0];
						Object v = graph.getModel().getValue(cell);
						//	System.out.println("Single selected cell=" + graph.getName(cell));

						vf = cell;//v;
					}
					Main.this.sheet.setBean(vf);
				}
			}
		});


		// mxGraphComponent is the actual Swing GUI component
		graphComponent = new mxGraphComponent(graph) {

			@Override
			public boolean isPanningEvent(MouseEvent event) {
				// Ctrl-drag will Pan
				return (event != null) ? event.isControlDown() : false;
			}

			@Override
			protected void installDoubleClickHandler() {
				graphControl.addMouseListener(new MouseAdapter() {

					@Override
					public void mouseReleased(MouseEvent e) {
						if (!e.isConsumed() && isEditEvent(e)) {
							Object cell = getCellAt(e.getX(), e.getY(), false);

							if (cell == null) {
								return;
							}
							if (getGraph().isCellEditable(cell) == false) {
								return;
							}
							if (!(cell instanceof doubleClickable)) {
								return;
							}

							doubleClickable dc = (doubleClickable) cell;
							dc.openEditor();
						}
					}
				});
			}
		};

		graphComponent.setConnectable(true);	// Outgoing edges are created by dragging from source vertex to destination vertex
		graphComponent.setPanning(true);		// Ctrl-drag will pan the view (as defined in isPanningEvent)
		graphComponent.setGridVisible(true);	// Grid
		graphComponent.addMouseWheelListener(new MouseWheelListener() {

			public void mouseWheelMoved(MouseWheelEvent e) {
				if (e.isControlDown()) {
					Main.this.mouseWheelZoom(e);	// Ctrl+wheel will zoom the view
				}
			}
		});

		keyboardHandler = new mxKeyboardHandler(graphComponent);	// Apply JGraph's default keybindings
		rubberband = new mxRubberband(graphComponent);		// Add selection-rectangle aka rubberband
		graphOutline = new mxGraphOutline(graphComponent);	// Add graph outline component (mini view)

		LibraryPalette shapesPalette = insertPalette("Basic", false);
		this.customPalette = insertPalette("Temp", true);

		shapesPalette.addTemplate(
				"Synchronous Data Flow process",
				"/fge/images/rounded_SDF.png",
				new SDFprocess("SDF process", 100, 100));

		shapesPalette.addTemplate(
				"Synchronous process",
				"/fge/images/rounded_SY.png",
				new SYprocess("SY process", 100, 100));

		shapesPalette.addTemplate(
				"Discrete Event process",
				"/fge/images/rounded_DE.png",
				new DEprocess("DE process", 100, 100));

		shapesPalette.addTemplate(
				"Continuous Time process",
				"/fge/images/rounded_CT.png",
				new CTprocess("CT process", 100, 100));

		shapesPalette.addTemplate(
				"Channel",
				"/fge/images/rectangle.png",
				new Fchannel("Ch", 40, 20));


		this.treeview = new TreeView(this);
		JScrollPane treeviewscroll = new JScrollPane(this.treeview.tree);
		
		JTabbedPane outline_tree = new JTabbedPane();
		outline_tree.add("Outline", graphOutline);
		outline_tree.add("Tree", treeviewscroll);
		
		
		// Palette and graph outline
		JSplitPane palette_outline = new JSplitPane(JSplitPane.VERTICAL_SPLIT, libraryPane, outline_tree);

		palette_outline.setDividerLocation(320);
		palette_outline.setResizeWeight(1);
		palette_outline.setDividerSize(6);
		palette_outline.setBorder(null);

		// <Palette and graph outline> and the graph
		JSplitPane paletteoutline_graph = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, palette_outline, graphComponent);

		paletteoutline_graph.setOneTouchExpandable(true);
		paletteoutline_graph.setDividerLocation(200);
		paletteoutline_graph.setDividerSize(6);
		paletteoutline_graph.setBorder(null);



		this.sheet = new propPanel(this);
		// <Palette and graph outline> and the graph
		JSplitPane paletteoutlinegraph_props = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, paletteoutline_graph, this.sheet);

		paletteoutlinegraph_props.setResizeWeight(1.0);		// When resizing window, let only left-pane expand
		paletteoutlinegraph_props.setOneTouchExpandable(true);
		paletteoutlinegraph_props.setDividerLocation(800);
		paletteoutlinegraph_props.setDividerSize(6);
		paletteoutlinegraph_props.setBorder(null);





		this.add(paletteoutlinegraph_props);
		this.installUndoRedo();
		this.createMenu();
	}

	public static void main(String[] args) {
		int w = 1000;
		int h = 600;
		Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();

		Main frame = new Main();
		frame.setSize(w, h);
		frame.setLocation(screen.width / 2 - w / 2, screen.height / 2 - h / 2);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setVisible(true);
	}
}
